import { _decorator, Component, Node, Prefab, instantiate, SpriteFrame, director } from "cc";
import { ActorManager } from "../Entity/Actor/ActorManager";
import DataManager from "../Global/DataManager";
import { JoyStickManager } from "../UI/JoyStickManager";
import { ResourceManager } from "../Global/ResourceManager";
import { EventEnum, PrefabPathEnum, SceneEnum, TexturePathEnum } from "../Enum";
import NetworkManager from "../Global/NetworkManager";
import ObjectPoolManager from "../Global/ObjectPoolManager";
import { BulletManager } from "../Entity/Bullet/BulletManager";
import { ApiMsgEnum, EntityTypeEnum, IClientInput, IMsgServerSync, InputTypeEnum, toFixed } from "../Common";
import EventManager from "../Global/EventManager";
import { deepClone } from "../Utils";

const { ccclass } = _decorator;

@ccclass("BattleManager")
export class BattleManager extends Component {
  private stage: Node;
  private ui: Node;
  private shouldUpdate = false;
  private pendingMsg = [];

  async start() {
    //清空
    this.clearGame();

    //资源加载和网络连接同步执行
    await Promise.all([this.loadRes(), this.connectServer()]);

    this.initGame();

    // 在场景初始化完毕之前，卡主别的玩家，准备好以后再告知服务器，等所有玩家都准备好以后才开始，这里就不做了
    NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgServerSync, this.listenServerSync, this);
    NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgGameEnd, this.listenGameEnd, this);
    EventManager.Instance.on(EventEnum.ClientSync, this.handleClientSync, this);
    EventManager.Instance.on(EventEnum.GameEnd, this.handleGameEnd, this);
  }

  clearGame() {
    //事件
    NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgServerSync, this.listenServerSync, this);
    NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgGameEnd, this.listenGameEnd, this);
    EventManager.Instance.off(EventEnum.ClientSync, this.handleClientSync, this);
    EventManager.Instance.off(EventEnum.GameEnd, this.handleGameEnd, this);

    //数据
    this.shouldUpdate = false;
    ObjectPoolManager.Instance.reset();
    DataManager.Instance.reset();

    //节点
    this.stage = DataManager.Instance.stage = this.node.getChildByName("Stage");
    this.ui = this.node.getChildByName("UI");
    this.stage.destroyAllChildren();
    this.ui.destroyAllChildren();
  }

  async loadRes() {
    const list = [];
    for (const type in PrefabPathEnum) {
      const p = ResourceManager.Instance.loadRes(PrefabPathEnum[type], Prefab).then((prefab) => {
        DataManager.Instance.prefabMap.set(type, prefab);
      });
      list.push(p);
    }
    for (const type in TexturePathEnum) {
      const p = ResourceManager.Instance.loadDir(TexturePathEnum[type], SpriteFrame).then((spriteFrames) => {
        DataManager.Instance.textureMap.set(type, spriteFrames);
      });
      list.push(p);
    }
    await Promise.all(list);
  }

  async connectServer() {
    if (!(await NetworkManager.Instance.connect().catch(() => false))) {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      await this.connectServer();
    }
  }

  async initGame() {
    this.initJoyStick();
    this.initShoot();
    this.initMap();
    this.shouldUpdate = true;
  }

  initJoyStick() {
    const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.JoyStick);
    const joySitck = instantiate(prefab);
    joySitck.setParent(this.ui);
    const jm = (DataManager.Instance.jm = joySitck.getComponent(JoyStickManager));
    jm.init();
  }

  initShoot() {
    const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.Shoot);
    const shoot = instantiate(prefab);
    shoot.setParent(this.ui);
  }

  initMap() {
    const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.Map1);
    const map = instantiate(prefab);
    map.setParent(this.stage);
  }

  update(dt: number) {
    if (!this.shouldUpdate) {
      return;
    }
    this.render();
    this.tick(dt);
  }

  tick(dt: number) {
    this.tickPlayer(dt);
    // this.tickGlobal(dt)
  }

  tickPlayer(dt: number) {
    for (const p of DataManager.Instance.state.actors) {
      const playerManager = DataManager.Instance.actorMap.get(p.id);
      if (!playerManager) {
        return;
      }
      playerManager.tick(dt);
    }
  }

  // tickGlobal(dt: number) {
  //     DataManager.Instance.applyInput({
  //         type: InputTypeEnum.TimePast,
  //         dt: toFixed(dt),
  //     })
  // }

  render() {
    this.renderPlayer();
    this.renderBullet();
  }

  renderPlayer() {
    for (const p of DataManager.Instance.state.actors) {
      let actorManager = DataManager.Instance.actorMap.get(p.id);
      if (!actorManager) {
        const playerPrefab = DataManager.Instance.prefabMap.get(p.type);
        const player = instantiate(playerPrefab);
        player.setParent(this.stage);
        actorManager = player.addComponent(ActorManager);
        DataManager.Instance.actorMap.set(p.id, actorManager);
        actorManager.init(p);
      } else {
        actorManager.render(p);
      }
    }
  }

  renderBullet() {
    for (const b of DataManager.Instance.state.bullets) {
      let bulletManager = DataManager.Instance.bulletMap.get(b.id);
      if (!bulletManager) {
        const bullet = ObjectPoolManager.Instance.get(b.type);
        bulletManager = bullet.getComponent(BulletManager) || bullet.addComponent(BulletManager);
        DataManager.Instance.bulletMap.set(b.id, bulletManager);
        bulletManager.init(b);
      } else {
        bulletManager.render(b);
      }
    }
  }

  handleClientSync(input: IClientInput) {
    const msg = {
      frameId: DataManager.Instance.frameId++,
      input,
    };
    NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, msg);

    //移动才做预测，射击不做
    if (input.type === InputTypeEnum.ActorMove) {
      DataManager.Instance.applyInput(input);
      this.pendingMsg.push(msg);
    }
  }

  listenServerSync({ lastFrameId, inputs }: IMsgServerSync) {
    //回滚上次服务器状态
    DataManager.Instance.state = DataManager.Instance.lastState;
    //应用服务器输入
    for (const input of inputs) {
      DataManager.Instance.applyInput(input);
    }
    //记录最新的服务器状态
    DataManager.Instance.lastState = deepClone(DataManager.Instance.state);

    //过滤本地输入
    this.pendingMsg = this.pendingMsg.filter((msg) => msg.frameId > lastFrameId);
    //应用本地输入
    for (const msg of this.pendingMsg) {
      DataManager.Instance.applyInput(msg.input);
    }
  }

  async handleGameEnd() {
    const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiGameEnd, { rid: DataManager.Instance.roomInfo.id });
    if (!success) {
      console.log(error);
      return;
    }
  }

  listenGameEnd() {
    this.clearGame();
    director.loadScene(SceneEnum.Hall);
  }
}
